package com.jintian.smart.kernel.module.impl;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;

@Slf4j
public class ModelClassLoader extends ClassLoader {
    private Set<String> notFounded = Collections.synchronizedSet(new HashSet<String>());

    private SmartUrlClassLoader loader = null;

    private final ModelImpl model;

    public ModelClassLoader(ModelImpl model, URL[] urls) {
        this(model, urls, ModelClassLoader.class.getClassLoader());
    }

    public ModelClassLoader(ModelImpl model, URL[] urls, ClassLoader parentLoader) {
        this.model = model;
        this.loader = new SmartUrlClassLoader(urls, parentLoader) {
            protected Class<?> findClass(String className) throws ClassNotFoundException {
                Class<?> clazz = null;
                try {
                    if ((clazz = super.findClass(className)) != null)
                        return clazz;
                } catch (Exception exception) {
                }
                if ((clazz = loadClassFromModel(className)) != null)
                    return clazz;
                throw new ClassNotFoundException(className + " not found");
            }
        };
    }

    public String toString() {
        return "<MCL for " + model.getName() + ", static=" + ((model.getClassLoader() == this) ? 1 : 0) + ">";
    }

    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        if (this.notFounded.contains(className))
            throw new ClassNotFoundException(className + " not found");
        if (log.isDebugEnabled()) {
            log.debug("try find class:" + className + " in model:" + model.getName());
        }
        Class<?> clazz = null;
        try {
            clazz = this.loader.loadClass(className);
        } catch (Exception exception) {
        }
        if (clazz != null)
            return clazz;
        this.notFounded.add(className);
        if (log.isDebugEnabled())
            log.debug(model.getName() + " load class '" + className + "' failed, try in other model.");
        throw new ClassNotFoundException(className + " not found");
    }

    @Override
    public URL getResource(String name) {
        return this.loader.getResource(name);
    }

    @Override
    public InputStream getResourceAsStream(String name) {
        return this.loader.getResourceAsStream(name);
    }

    @Override
    public Enumeration<URL> getResources(String name) throws IOException {
        return this.loader.getResources(name);
    }

    @Override
    protected URL findResource(String name) {
        return this.loader.findResource(name);
    }

    @Override
    protected Enumeration<URL> findResources(String name) throws IOException {
        return this.loader.findResources(name);
    }

    private Class<?> loadClassFromModel(String className) {
        Class<?> clazz = null;
        if (model.getParent() != null) {
            try {
                clazz = model.getParent().getClassLoader().loadClass(className);
            } catch (Exception exception) {
            }
            if (clazz != null)
                return clazz;
        }
        return null;
    }
}
